package top.archiesean.authorize.config;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;

import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2AccessTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.web.authentication.*;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import top.archiesean.authorize.support.core.CustomOAuth2TokenCustomizer;
import top.archiesean.authorize.support.core.FormIdentityLoginConfigurer;
import top.archiesean.authorize.support.core.SeanDaoAuthenticationProvider;
import top.archiesean.authorize.support.handler.AuthenticationFailureEventHandler;
import top.archiesean.authorize.support.handler.AuthenticationSuccessEventHandler;
import top.archiesean.authorize.support.password.OAuth2PasswordAuthenticationConverter;
import top.archiesean.authorize.support.password.OAuth2PasswordAuthenticationProvider;
import top.archiesean.common.core.constant.SecurityConstants;
import top.archiesean.common.security.service.RedisOAuth2AuthorizationConsentService;
import top.archiesean.common.security.service.RedisOAuth2AuthorizationService;

import java.time.Duration;
import java.util.Arrays;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author ArchieSean
 * @description 认证服务配置类
 * @date 2024-01-04 23:57
 */

@Configuration
@RequiredArgsConstructor
public class AuthorizationServerConfig {

    private final BCryptPasswordEncoder bCryptPasswordEncoder;

    private final JdbcTemplate jdbcTemplate;

    private final RedisOAuth2AuthorizationService redisOAuth2AuthorizationService;

    private final RedisOAuth2AuthorizationConsentService redisOAuth2AuthorizationConsentService;


    /**
     * 客户单注册bean实例
     *
     * @return RegisteredClientRepository
     */
    @Bean
    public RegisteredClientRepository registeredClientRepository() {
        RegisteredClient oidcClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("oauth2-client")
                .clientSecret(bCryptPasswordEncoder.encode("ArchieSean"))
                //客户端认证方式
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                //配置授权的方式
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .authorizationGrantType(AuthorizationGrantType.PASSWORD)
                //授权成功，跳转的地址
                .redirectUri("https://www.baidu.com")
                //退出之后跳转的地址
                .postLogoutRedirectUri("https://fanyi.baidu.com")
                .scope("user")
                .scope("admin")
                //客户端配置，设置用户是否需要确认操作
                .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
                //令牌设置
                .tokenSettings(TokenSettings.builder().accessTokenFormat(OAuth2TokenFormat.REFERENCE)
                        //令牌过期时间
                        .accessTokenTimeToLive(Duration.of(30, TimeUnit.MINUTES.toChronoUnit()))
                        //刷新令牌7天
                        .refreshTokenTimeToLive(Duration.of(7, TimeUnit.DAYS.toChronoUnit()))
                        .build())
                .build();
        //实例化到db
        JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
        RegisteredClient client = jdbcRegisteredClientRepository.findByClientId(oidcClient.getClientId());
        if(Objects.isNull(client)){
            jdbcRegisteredClientRepository.save(oidcClient);
        }
        return jdbcRegisteredClientRepository;
    }


    /**
     * @param http HttpSecurity
     * @return SecurityFilterChain
     * @throws Exception ex
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
            throws Exception {
        OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
                new OAuth2AuthorizationServerConfigurer();
        http.apply(
                authorizationServerConfigurer
                        //令牌端点
                        .tokenEndpoint(tokenEndpoint ->
                                //在原有的基础上扩展密码模式
                                tokenEndpoint
                                        //令牌转换器
                                        .accessTokenRequestConverter(accessTokenRequestConverter())
                                        //授权成功处理器
                                        .accessTokenResponseHandler(new AuthenticationSuccessEventHandler())
                                        //授权失败处理器
                                        .errorResponseHandler(new AuthenticationFailureEventHandler())
                        )
                        //客户端身份认证
                        .clientAuthentication(clientAuthentication ->
                                //授权码认证失败处理器
                                clientAuthentication.errorResponseHandler(new AuthenticationFailureEventHandler())
                        )
                        .authorizationEndpoint(authorizationEndpoint ->
                                //授权码模式，授权确认页面端点
                                authorizationEndpoint.consentPage(SecurityConstants.CUSTOM_CONSENT_PAGE_URI))
        );
        http.apply(authorizationServerConfigurer
                        //注册客户端信息的持久层
                        .registeredClientRepository(registeredClientRepository())
                        //令牌信息存储服务，自定义实现使用redis缓存
                        .authorizationService(redisOAuth2AuthorizationService))
                //授权表决状态存储
                .authorizationConsentService(redisOAuth2AuthorizationConsentService)
                .authorizationServerSettings(AuthorizationServerSettings.builder()
                        //如果不设置颁发者标识，会从当前请求中解析
                        //.issuer(SecurityConstants.PROJECT_ISSUER)
                        .build())
        ;

        AntPathRequestMatcher[] requestMatchers = new AntPathRequestMatcher[]{
                AntPathRequestMatcher.antMatcher("/token/**"), AntPathRequestMatcher.antMatcher("/actuator/**"),
                AntPathRequestMatcher.antMatcher("/css/**"), AntPathRequestMatcher.antMatcher("/error")};

        http
                .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
                .authorizeHttpRequests(authorize ->
                        authorize
                                .requestMatchers(requestMatchers).permitAll()
                                .anyRequest().authenticated())
                .csrf(CsrfConfigurer::disable)
                //基于授权码认证的统一登录页面配置
                .apply(new FormIdentityLoginConfigurer());

        DefaultSecurityFilterChain chain = http.build();
        //添加自定义密码认证主处理器
        addingAdditionalAuthenticationProvider(http);

        return chain;
    }


    /**
     * 自定义oauth2token 配置
     *
     * @return OAuth2TokenGenerator
     */
    @Bean
    public OAuth2TokenGenerator<?> tokenGenerator() {
        OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
        //设置自定义oauth2token
        accessTokenGenerator.setAccessTokenCustomizer(new CustomOAuth2TokenCustomizer());
        return new DelegatingOAuth2TokenGenerator(accessTokenGenerator, new OAuth2RefreshTokenGenerator());
    }

    /**
     * 请求转换器：用于从HttpServlet请求提取OAuth2授权请求时使用的预处理器
     *
     * @return AuthenticationConverter
     */
    private AuthenticationConverter accessTokenRequestConverter() {
        //DelegatingAuthenticationConverter： 认证委托转换器，需要包含需要支持的所有认证方式的转换器
        return new DelegatingAuthenticationConverter(
                Arrays.asList(
                        //密码模式转换器
                        new OAuth2PasswordAuthenticationConverter(),
                        //refreshToken转换器: 用于令牌刷新支持
                        new OAuth2RefreshTokenAuthenticationConverter(),
                        //客户端模式
                        new OAuth2ClientCredentialsAuthenticationConverter(), //用于客户端获取令牌
                        //授权码模式
                        new OAuth2AuthorizationCodeAuthenticationConverter(), //用于授权码获取
                        new OAuth2AuthorizationCodeRequestAuthenticationConverter() //用于根据授权码获取令牌
                )
        );
    }

    private static void addingAdditionalAuthenticationProvider(HttpSecurity http) {
        AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
        OAuth2AuthorizationService authorizationService = http.getSharedObject(OAuth2AuthorizationService.class);
        OAuth2TokenGenerator<?> tokenGenerator = http.getSharedObject(OAuth2TokenGenerator.class);

        OAuth2PasswordAuthenticationProvider passwordAuthenticationProvider =
                new OAuth2PasswordAuthenticationProvider(authenticationManager, authorizationService, tokenGenerator);
        //处理UsernamePasswordAuthenticationToken
        http.authenticationProvider(new SeanDaoAuthenticationProvider());
        //密码模式支持
        http.authenticationProvider(passwordAuthenticationProvider);
    }

}
